
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Set;



public class Node {
	int lbit;
	/*
	 * This is the length of the virutal id space used.
	 */

	public BucketSet buckets;
	/*
	 * This is the set of buckets corresponding to each B_k(for this node);
	 * It is used as a routing table for this node.
	 */

	long myvid;
	/*
	 * virtual id of the node
	 */

	long myuid;
	/*
	 * unique id of the node
	 */

	int round=1;

	/*
	 * it is the round number used in the routing table construction process
	 */

	public Set<Bucket> rendezvousStore;
	/*
	 * If a node acts as a rendezvous point for some its local subtree, then it will store this information 
	 * here. 
	 * Here we will store all the published buckets as a list, (we won't define seperate list for rendezvous points
	 * at different levels. 
	 */

	public Hashtable<Integer,  Set<Prefix>> prefixtable;
	List<Message> msgQueue;
	/*
	 * A node uses this msgQueue to communicate with all
	 * the World
	 */

	public Set<Long> myNeighbors;
	/*
	 * Its a list of double values, which contains the neighbors for this particular node
	 */

	World myworld;
	/*
	 *  We have this function to call methods from myworld object
	 */
	public static boolean local_debug_flag = true;
	/*
	 * Local debug flag, if true then it prints the debug messages, else it doesn't
	 */

	String newtable = "";
	public Hashtable <Long, Boolean> lsadone; // see if the lsa for this destination has been processed or  not
	public long lsacount = 0; // number of lsa processed by the node.
	String oldtable = "";
	public long viroControlPacketCount = 0;
	public Node(int l, List<Message> queue, long uid, long vid, Set<Long> myNeighbors, World myworld){
		/*
		 * Constructor for a node
		 */
		lbit = l; myvid = vid; myuid = uid; 
		msgQueue = queue;
		this.myworld = myworld;
		this.myNeighbors = myNeighbors;
		buckets = new BucketSet();
		prefixtable = new Hashtable<Integer, Set<Prefix>> ();
		lsadone = new Hashtable<Long, Boolean>();
		lsacount = 0;
		viroControlPacketCount = 0;
		//initialize the prefixtable
		for (int i = 0; i <= lbit;i++){
			Prefix p = new Prefix();
			p.len = lbit-i;
			if (i != 0){
				p.prefix = ((myvid >> (i-1)) ^ 1)<<(i-1);
				p.len++;
			}else{
				p.prefix = myvid;
			}
			p.maxlen = lbit;
			Set<Prefix> set = new HashSet<Prefix>();
			set.add(p);
			prefixtable.put(i, set);
		}
		//fill the neighbor information( in the buckets
		for (Iterator<Long> it = myNeighbors.iterator(); it.hasNext(); ){
			long n1 = it.next();
			int b = World.delta(myvid,n1);
			Iterator<Prefix> prefix_it = prefixtable.get(b).iterator();
			while (prefix_it.hasNext()){
				Bucket b1 = new Bucket();
				b1.dest = n1;
				b1.nexthop = n1;
				b1.gw = myvid;
				b1.prefix = prefix_it.next();
				b1.bucket_level = b;
				buckets.add(b1);
			}
		}

		// Initialize the rendezvous store
		rendezvousStore = new HashSet<Bucket>();
	}

	public boolean isRoutingTableChanged(){
		return !newtable.equals(oldtable);
	}

	public String routing_table_toString(){
		/*
		 * This function will convert the routing table info contained in the object
		 * buckets to a string object, so that it can be used to print in text format
		 */
		String str= "RoutingTable at Node(vid): "+myvid + " [" + World.toBinaryString(myvid) + "]\n";
		for (int i = 1; i <= lbit; i++){
			str = str + "["+ i + "] " + buckets2str(i) + "\n";
		}
		return str;
	}

	public String buckets2str(int i) {
		// 
		String retstr = "";
		Iterator<Bucket> buck_it = buckets.get(i).iterator();
		if (buck_it.hasNext() == false){
			return retstr;
		}
		retstr=(Bucket)buck_it.next()+"";
		while(buck_it.hasNext()){
			retstr=retstr+"\n    "+(Bucket)buck_it.next();
		}
		return retstr;
	}

	// Not referenced any where... Dead code
	public String rendezvous_point_toString(){
		/*
		 * This function will convert the rendezvous point info contained in the object
		 * rendezvousStore to a string object, so that it can be used to print in text format
		 */
		int i = 0;
		String str= "Rendezvous store at Node(vid): "+myvid + " [" + World.toBinaryString(myvid) + "]\n";
		for (Iterator<Bucket> rdv_iterator = rendezvousStore.iterator(); rdv_iterator.hasNext();) {
			Bucket rdvinfo = (Bucket) rdv_iterator.next();
			str = str + "["+ i + "] " +rdvinfo + "\n";
			i++;
		}
		return str;
	}

	public String toString(){
		//return this.routing_table_toString() + this.rendezvous_point_toString()+this.prefixtable_toString();
		return this.routing_table_toString() + "My Physical Neighbors: \n"+ myNeighbors + this.prefixtable_toString();
	}

	public String prefixtable_toString() {
		String return_string = "Prefix Table: ";
		for (int i = 1; i <=lbit; i++){
			if (prefixtable.containsKey(i)){
				return_string += "\nAt level: " + i+ "-->\t"+prefixtable.get(i);
			}
		}
		return return_string+"\n";
	}

	public void constructRoutingTable(){
		/*
		 * It runs one round of routing table construction
		 */
		oldtable = toString();
		/*
		 * Pseudocode:
		 * 
		 * First see which round it is
		 * Do I already have this bucket filled up?
		 * 		if yes:
		 * 			create a message with:
		 * 				src = myvid;
		 * 				dest = World.getRdvVID(myvid, k);
		 * 				messagetype = Message.RDV_PUBLISH
		 * 				payload = (Object) bucket[k]
		 * 			Now publish this information
		 * 			myworld.routePacketLogically(msg);
		 * 		else :
		 * 			create a query packet with:
		 * 				src = myvid;
		 * 				dest =  World.getRdvVID(myvid, k);
		 * 				messagetype = Message.RDV_QUERY
		 * 			Now query for the information
		 * 			myworld.routePacketLogically(msg);
		 */

		// First publish all my neighbors information
		for (Iterator<Long> it = myNeighbors.iterator(); it.hasNext(); ){
			long n1 = it.next();
			int b = World.delta(myvid,n1);
			if (round >= b){
				Iterator<Prefix> prefix_handle = prefixtable.get(b).iterator();
				int match = 0; 
				Prefix bestMatch = null;
				// how well it matches with the prefix, e.g. if the prefix is 01* and n1 = 011 then match is 1, 
				// similarly if the prfix is 101 and n1 = 011 then match is 0.
				while (prefix_handle.hasNext()) {
					Prefix candidate = prefix_handle.next();
					int matchval = candidate.getMatchValue(n1);
					if (match < matchval ){
						bestMatch = candidate;
						match = matchval;
					}
				}
				if (match == 0){
					World.println("Alert: No best match found for the node id: "+ World.toBinaryString(n1)+"\n Not publishing the Neighbor information!", local_debug_flag);
					continue;
				}
				// Now publish the routing entry for this prefix
				World.println("Best Match: "+ bestMatch, local_debug_flag);
				Bucket b1 = new Bucket();
				b1.dest = n1;
				b1.nexthop = n1;
				b1.gw = myvid;
				b1.bucket_level = b;
				b1.prefix = bestMatch;	
				buckets.add(b1);
				publish(b1);
			}
		}

		int roundn = round > lbit ? lbit:round;
		for (int k = 1; k <= roundn; k++){
			Iterator<Prefix> prefix_handle = prefixtable.get(k).iterator();
			while (prefix_handle.hasNext()) {
				Bucket bk = new Bucket();
				bk.dest = -1;
				bk.nexthop = -1;
				bk.gw = -1;
				bk.bucket_level = k;
				bk.prefix = prefix_handle.next();
				query(bk);
			}
		}
		/*
		 * Now we try to build bridges:
		 * the idea is to for each empty bucket b
		 * 		query all the filled buckets between b to min (round,lbit) for the bridge one by one
		 */

		// run an external for loop to find all the empty buckets.
		for (int k = 1; k <= roundn; k++){
			// see if the bucket is empty or not
			if (buckets.get(k).isEmpty() == true){
				// now query the logical neighbors in the higher order buckets
				for (int kprime = k+1; kprime <= roundn; kprime++){
					// see if the bucket is empty or not
					// I will make query if I am acting as a bridge for this level, 
					// else somebody else will do the query for me.
					if (buckets.get(kprime).isEmpty() == false){
						// if the bucket is not empty then query for the possible bridges
						// if a "bridge" was found then done. else continue
						World.println("BRIDGE QUERY for bucket: "+k+" from "+myvid + " to " + "my bucket at level: "+kprime,World.debug);
						if (query_bridge(k,kprime) == true){
							// Publish the result of the bridge query to the rendezvous point so that other nodes can also learn it.
							World.println("Have to publish the result of bridge query", local_debug_flag);
							break;
						}
					}
				}
			}
		}
		round++;
		newtable = toString();
		if (!newtable.equals(oldtable)){
			boolean toPrint = false;
			World.println("Updated Routing Table: ",toPrint);
			World.println("Old Table: "+oldtable, toPrint);
			World.println("New Table: "+newtable, toPrint);
		}
	}

	// Comented in the code... Understand this...
	public void publishPrefixes(int k) {
		/*
		 * This function publishes the prefixes at kth level
		 */
		Message msg = new Message();
		msg.src = myvid;
		// k could be k-1 or k+1 check for it.
		msg.dest = World.getRdvVID(myvid, k);
		msg.type = Message.RDV_PUBLISH_PREFIX;
		// set of prefixes to be published
		msg.payload = prefixtable.get(k);
		// set the level of the prefixes to be published
		msg.payload1 = k;
		//myworld.routePacketLogically(msg);
		myworld.routepacket(msg);

	}

	public boolean query_bridge(int k, int kprime) {
		// query the kprime'th Bucket of mine to reach kth bucket.
		boolean bridgeFound = false;
		Bucket bucket = new Bucket();
		bucket.dest = bucket.gw = -1;
		bucket.bucket_level = k;

		Iterator<Prefix> prefix_it = prefixtable.get(k).iterator();	
		// get all the prefix at kth bucket level, ideally there should be only one prefix at this level.
		while (prefix_it.hasNext()){
			bucket.prefix = prefix_it.next();
			Iterator<Bucket> it  = buckets.get(kprime).iterator();
			
			// get the routing entry to reach kprime bucket
			// if there are multiple buckets at kprime level then ask the each one of them for the bridge.
			while (it.hasNext()){
				Bucket b = it.next();
				Message msg = new Message();
				msg.src = myvid;
				msg.dest = World.getRdvVID(b.dest, kprime);
				msg.type = Message.RDV_BRIDGE_QUERY;
				msg.payload =new Bucket(bucket);

				// only query if I am acting as a gateway
				World.println("Sending QUERY for BDG in a message: "+msg,World.debug);
				bridgeFound = (myworld.routepacket(msg) >= 0);
				if (bridgeFound == true){
					return bridgeFound;
				}
			}
		}
		return bridgeFound;
	}

	public void publish(Bucket bucket) {
		/*
		 * This function publishes the information at kth level
		 */
		Message msg = new Message();
		msg.src = myvid;
		// k could be k-1 or k+1 check for it.
		msg.dest = World.getRdvVID(myvid, bucket.bucket_level);
		msg.type = Message.RDV_PUBLISH;
		msg.payload = new Bucket(bucket);
		//myworld.routePacketLogically(msg);
		myworld.routepacket(msg);
	}

	public void query(Bucket bucket) {
		/*
		 * This function performs for a connectivity information at kth level
		 */
		Message msg = new Message();
		msg.src = myvid;
		// k could be k-1 or k+1 check for it.
		msg.dest = World.getRdvVID(myvid, bucket.bucket_level);
		msg.type = Message.RDV_QUERY;
		msg.payload = bucket;
		//myworld.routePacketLogically(msg);
		myworld.routepacket(msg);

	}

	public void clearBucket(int bucket_level) {
		// TODO Auto-generated method stub
		Set<Bucket> toremove = buckets.get(bucket_level);
		Iterator<Bucket> it  = toremove.iterator();
		while (it.hasNext()){
			buckets.remove(it.next());
		}	
	}
	public void processLsa(long node_vid, long sender_vid){
		lsacount++;
		if (!lsadone.containsKey(node_vid)){
			lsadone.put((Long)node_vid,true);
			//process this packet by forwarding it to all the other nodes:
			Iterator<Long> neighbors = myNeighbors.iterator();
			while (neighbors.hasNext()){
				lsacount++;
				Long nextNeighbor = neighbors.next();
				if ((long)nextNeighbor != sender_vid){
					myworld.node.get(nextNeighbor).processLsa(node_vid,myvid );
				}
			}
		}
	}
	public void startLsa(){
		lsacount++;
		Iterator<Long> neighbors = myNeighbors.iterator();
		while (neighbors.hasNext()){
			lsacount++;
			Long nextNeighbor = neighbors.next();
			myworld.node.get(nextNeighbor).processLsa(myvid,myvid );
		}
	}


}
